home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / lib / ruby / 1.8 / gserver.rb < prev    next >
Text File  |  2007-02-12  |  7KB  |  254 lines

  1. #
  2. # Copyright (C) 2001 John W. Small All Rights Reserved
  3. #
  4. # Author::        John W. Small
  5. # Documentation:: Gavin Sinclair
  6. # Licence::       Freeware.
  7. #
  8. # See the class GServer for documentation.
  9. #
  10.  
  11. require "socket"
  12. require "thread"
  13.  
  14. #
  15. # GServer implements a generic server, featuring thread pool management,
  16. # simple logging, and multi-server management.  See HttpServer in 
  17. # <tt>xmlrpc/httpserver.rb</tt> in the Ruby standard library for an example of
  18. # GServer in action.
  19. #
  20. # Any kind of application-level server can be implemented using this class.
  21. # It accepts multiple simultaneous connections from clients, up to an optional
  22. # maximum number.  Several _services_ (i.e. one service per TCP port) can be
  23. # run simultaneously, and stopped at any time through the class method
  24. # <tt>GServer.stop(port)</tt>.  All the threading issues are handled, saving
  25. # you the effort.  All events are optionally logged, but you can provide your
  26. # own event handlers if you wish.
  27. #
  28. # === Example
  29. #
  30. # Using GServer is simple.  Below we implement a simple time server, run it,
  31. # query it, and shut it down.  Try this code in +irb+:
  32. #
  33. #   require 'gserver'
  34. #
  35. #   #
  36. #   # A server that returns the time in seconds since 1970.
  37. #   # 
  38. #   class TimeServer < GServer
  39. #     def initialize(port=10001, *args)
  40. #       super(port, *args)
  41. #     end
  42. #     def serve(io)
  43. #       io.puts(Time.now.to_i)
  44. #     end
  45. #   end
  46. #
  47. #   # Run the server with logging enabled (it's a separate thread).
  48. #   server = TimeServer.new
  49. #   server.audit = true                  # Turn logging on.
  50. #   server.start 
  51. #
  52. #   # *** Now point your browser to http://localhost:10001 to see it working ***
  53. #
  54. #   # See if it's still running. 
  55. #   GServer.in_service?(10001)           # -> true
  56. #   server.stopped?                      # -> false
  57. #
  58. #   # Shut the server down gracefully.
  59. #   server.shutdown
  60. #   
  61. #   # Alternatively, stop it immediately.
  62. #   GServer.stop(10001)
  63. #   # or, of course, "server.stop".
  64. #
  65. # All the business of accepting connections and exception handling is taken
  66. # care of.  All we have to do is implement the method that actually serves the
  67. # client.
  68. #
  69. # === Advanced
  70. #
  71. # As the example above shows, the way to use GServer is to subclass it to
  72. # create a specific server, overriding the +serve+ method.  You can override
  73. # other methods as well if you wish, perhaps to collect statistics, or emit
  74. # more detailed logging.
  75. #
  76. #   connecting
  77. #   disconnecting
  78. #   starting
  79. #   stopping
  80. #
  81. # The above methods are only called if auditing is enabled.
  82. #
  83. # You can also override +log+ and +error+ if, for example, you wish to use a
  84. # more sophisticated logging system.
  85. #
  86. class GServer
  87.  
  88.   DEFAULT_HOST = "127.0.0.1"
  89.  
  90.   def serve(io)
  91.   end
  92.  
  93.   @@services = {}   # Hash of opened ports, i.e. services
  94.   @@servicesMutex = Mutex.new
  95.  
  96.   def GServer.stop(port, host = DEFAULT_HOST)
  97.     @@servicesMutex.synchronize {
  98.       @@services[host][port].stop
  99.     }
  100.   end
  101.  
  102.   def GServer.in_service?(port, host = DEFAULT_HOST)
  103.     @@services.has_key?(host) and
  104.       @@services[host].has_key?(port)
  105.   end
  106.  
  107.   def stop
  108.     @connectionsMutex.synchronize  {
  109.       if @tcpServerThread
  110.         @tcpServerThread.raise "stop"
  111.       end
  112.     }
  113.   end
  114.  
  115.   def stopped?
  116.     @tcpServerThread == nil
  117.   end
  118.  
  119.   def shutdown
  120.     @shutdown = true
  121.   end
  122.  
  123.   def connections
  124.     @connections.size
  125.   end
  126.  
  127.   def join
  128.     @tcpServerThread.join if @tcpServerThread
  129.   end
  130.  
  131.   attr_reader :port, :host, :maxConnections
  132.   attr_accessor :stdlog, :audit, :debug
  133.  
  134.   def connecting(client)
  135.     addr = client.peeraddr
  136.     log("#{self.class.to_s} #{@host}:#{@port} client:#{addr[1]} " +
  137.         "#{addr[2]}<#{addr[3]}> connect")
  138.     true
  139.   end
  140.  
  141.   def disconnecting(clientPort)
  142.     log("#{self.class.to_s} #{@host}:#{@port} " +
  143.       "client:#{clientPort} disconnect")
  144.   end
  145.  
  146.   protected :connecting, :disconnecting
  147.  
  148.   def starting()
  149.     log("#{self.class.to_s} #{@host}:#{@port} start")
  150.   end
  151.  
  152.   def stopping()
  153.     log("#{self.class.to_s} #{@host}:#{@port} stop")
  154.   end
  155.  
  156.   protected :starting, :stopping
  157.  
  158.   def error(detail)
  159.     log(detail.backtrace.join("\n"))
  160.   end
  161.  
  162.   def log(msg)
  163.     if @stdlog
  164.       @stdlog.puts("[#{Time.new.ctime}] %s" % msg)
  165.       @stdlog.flush
  166.     end
  167.   end
  168.  
  169.   protected :error, :log
  170.  
  171.   def initialize(port, host = DEFAULT_HOST, maxConnections = 4,
  172.     stdlog = $stderr, audit = false, debug = false)
  173.     @tcpServerThread = nil
  174.     @port = port
  175.     @host = host
  176.     @maxConnections = maxConnections
  177.     @connections = []
  178.     @connectionsMutex = Mutex.new
  179.     @connectionsCV = ConditionVariable.new
  180.     @stdlog = stdlog
  181.     @audit = audit
  182.     @debug = debug
  183.   end
  184.  
  185.   def start(maxConnections = -1)
  186.     raise "running" if !stopped?
  187.     @shutdown = false
  188.     @maxConnections = maxConnections if maxConnections > 0
  189.     @@servicesMutex.synchronize  {
  190.       if GServer.in_service?(@port,@host)
  191.         raise "Port already in use: #{host}:#{@port}!"
  192.       end
  193.       @tcpServer = TCPServer.new(@host,@port)
  194.       @port = @tcpServer.addr[1]
  195.       @@services[@host] = {} unless @@services.has_key?(@host)
  196.       @@services[@host][@port] = self;
  197.     }
  198.     @tcpServerThread = Thread.new {
  199.       begin
  200.         starting if @audit
  201.         while !@shutdown
  202.           @connectionsMutex.synchronize  {
  203.              while @connections.size >= @maxConnections
  204.                @connectionsCV.wait(@connectionsMutex)
  205.              end
  206.           }
  207.           client = @tcpServer.accept
  208.           @connections << Thread.new(client)  { |myClient|
  209.             begin
  210.               myPort = myClient.peeraddr[1]
  211.               serve(myClient) if !@audit or connecting(myClient)
  212.             rescue => detail
  213.               error(detail) if @debug
  214.             ensure
  215.               begin
  216.                 myClient.close
  217.               rescue
  218.               end
  219.               @connectionsMutex.synchronize {
  220.                 @connections.delete(Thread.current)
  221.                 @connectionsCV.signal
  222.               }
  223.               disconnecting(myPort) if @audit
  224.             end
  225.           }
  226.         end
  227.       rescue => detail
  228.         error(detail) if @debug
  229.       ensure
  230.         begin
  231.           @tcpServer.close
  232.         rescue
  233.         end
  234.         if @shutdown
  235.           @connectionsMutex.synchronize  {
  236.              while @connections.size > 0
  237.                @connectionsCV.wait(@connectionsMutex)
  238.              end
  239.           }
  240.         else
  241.           @connections.each { |c| c.raise "stop" }
  242.         end
  243.         @tcpServerThread = nil
  244.         @@servicesMutex.synchronize  {
  245.           @@services[@host].delete(@port)
  246.         }
  247.         stopping if @audit
  248.       end
  249.     }
  250.     self
  251.   end
  252.  
  253. end
  254.